﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Drawing.Drawing2D;
using System.Windows.Forms;
using System.Threading;
using System.Drawing.Imaging;
using System.Diagnostics;
using DragDropLib;
using ComIDataObject = System.Runtime.InteropServices.ComTypes.IDataObject;
using DataObject = System.Windows.Forms.DataObject;

namespace AngelPerez.Taringa.PostEdit {
	public partial class MainWindow : Form {
		List<Control> loadedControls = null;
		static Control currentSelectedControl = null;
		Pen selectedControlPen = null;
		BackgroundWorker controlEventUpdateWorker = null;
		bool forceControlUpdate = false;
		Point currentSelectedControlLocation;
		uint controlCount = 0;
		uint logoClicks = 0;
		Point viewportMouseDownPosition;
		Point viewportMouseMovePosition;
		bool viewportMouseDown = false;
		ContentAlignment viewportResizeMode = ContentAlignment.TopLeft;

		public static object GetPropValue(object src, string propName) {
			return src.GetType().GetProperty(propName).GetValue(src, null);
		}

		public MainWindow() {
			InitializeComponent();

			this.DoubleBuffered = true;

			loadedControls = new List<Control>();
			currentSelectedControlLocation = new Point(0, 0);

			selectedControlPen = new Pen(new SolidBrush(Color.FromArgb(64, 0, 0, 0)));
			selectedControlPen.DashPattern = new float[] {4f, 4f};
			selectedControlPen.DashStyle = DashStyle.Custom;

			controlEventUpdateWorker = new BackgroundWorker();
			controlEventUpdateWorker.DoWork += (sender, args) => {
				while (loadedControls.Count != viewport.Controls.Count || forceControlUpdate) {
					UpdateObjectEventsAsync();
				}
			};
		}

		private bool IsControlLoaded(Control control) {
			try {
				return (loadedControls.Find(new Predicate<Control>((args) => {
					return args.Name == control.Name;
				})) == null);
			} catch {
				return false;
			}
		}

		private void UpdateObjectEventsAsync() {
			foreach (Control control in viewport.Controls) {
				IAsyncResult result = control.BeginInvoke(new MethodInvoker(() => {
					UpdateSingleObjectEvents(control);
				}));

				control.EndInvoke(result);
			}
		}

		private void UpdateSingleObjectEvents(Control control) {
			Point MouseDownLocation = new Point(0, 0);

			control.Cursor = Cursors.SizeAll;

			if (!IsControlLoaded(control)) {
				loadedControls.Add(control);
			}

			control.MouseDown += (sender, args) => {
				if (args.Button == MouseButtons.Left) {
					MouseDownLocation = args.Location;
					control.Tag = (string)"mousedown";
				}
			};

			control.MouseUp += (sender, args) => {
				if (args.Button == MouseButtons.Left) {
					MouseDownLocation = args.Location;
					control.Tag = (string)"";

					currentSelectedControl = control;
					currentSelectedControlLocation = control.Location;

					viewport.Refresh();

					try {
						propertyBox.ChangeSelectedObject(control);
					} catch {
						MessageBox.Show(this, "El modelo de propiedades del objeto es inválido.", "PostEdit", MessageBoxButtons.OK, MessageBoxIcon.Error);
					}
				}
			};

			control.MouseMove += (sender, args) => {
				if ((string)control.Tag == "mousedown" && currentSelectedControl == control) {
					Point position = viewport.PointToClient(MousePosition);
					control.Location = new Point(position.X - MouseDownLocation.X, position.Y - MouseDownLocation.Y);
					control.Cursor = Cursors.SizeAll;
					viewport.Refresh();
				}
			};

			control.LocationChanged += (sender, args) => {
				viewport.Refresh();
				base.OnLocationChanged(args);
			};

			control.SizeChanged += (sender, args) => {
				viewport.Refresh();
				base.OnSizeChanged(args);
			};
		}

		private void UpdateObjectEvents() {
			// Detect when a control is focused.
			foreach (Control control in viewport.Controls) {
				UpdateSingleObjectEvents(control);
			}	
		}

		protected override void OnLoad(EventArgs e) {
			controlEventUpdateWorker.RunWorkerAsync();
			base.OnLoad(e);
		}

		private void pictureBox2_Click(object sender, EventArgs e) {
			ToolboxWindow toolbox = new ToolboxWindow();
			toolbox.Show(this);
		}

		private void OnDragDrop(object sender, DragEventArgs e) {
#if !DEBUG
			try {
#endif
				TreeNode c = e.Data.GetData(e.Data.GetFormats()[0]) as TreeNode;
				Control newControl = null;

				if (c != null) {
					switch (c.Tag.ToString()) {
						case "PEToolboxCategory":				// Toolbox category. Do nothing.
							return;

						case "PELabel":									// Label.
							newControl = new PELabel();
							newControl.Size = new Size(100, 24);
							
							break;

						default:
							goto unsupported;
					}

					controlCount++;
					newControl.Size = new Size(100, 24);
					newControl.Name = c.Text + " #" + controlCount.ToString();
					newControl.Text = c.Text;
					newControl.Location = viewport.PointToClient(MousePosition);
					viewport.Controls.Add(newControl);
					viewport.Refresh();

#if !DEBUG
					try {
#endif
						propertyBox.ChangeSelectedObject(newControl);
#if !DEBUG
					} catch(Exception exception) {
						goto invalid_prop;
					}
#endif

					UpdateObjectEventsAsync();
				}
#if !DEBUG
			} catch(Exception exception) {
				goto unsupported;
			}
#endif

			return;

		unsupported:
			MessageBox.Show(this, "Operación no soportada.", "PostEdit", MessageBoxButtons.OK, MessageBoxIcon.Error);
			return;

		invalid_prop:
			MessageBox.Show(this, "El modelo de propiedades del objeto es inválido.", "PostEdit", MessageBoxButtons.OK, MessageBoxIcon.Error);
			return;
		}

		private void OnDragOver(object sender, DragEventArgs e) {
			e.Effect = DragDropEffects.Copy;
			IDropTargetHelper dropHelper = (IDropTargetHelper)new DragDropHelper();
			dropHelper.DragLeave();
		}

		private void OnViewportPaint(object sender, PaintEventArgs e) {
			e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;

			if (currentSelectedControl != null) {
				// Draw resize box.
				e.Graphics.DrawRectangle(new Pen(new SolidBrush(Color.FromArgb(32, 0, 0, 0))), new Rectangle(currentSelectedControl.Location.X - 4, currentSelectedControl.Location.Y - 4, currentSelectedControl.Width + 9, currentSelectedControl.Height + 9));
				e.Graphics.DrawRectangle(new Pen(new SolidBrush(Color.FromArgb(16, 0, 0, 0))), new Rectangle(currentSelectedControl.Location.X - 5, currentSelectedControl.Location.Y - 5, currentSelectedControl.Width + 11, currentSelectedControl.Height + 11));
				e.Graphics.DrawRectangle(new Pen(new SolidBrush(Color.FromArgb(8, 0, 0, 0))), new Rectangle(currentSelectedControl.Location.X - 6, currentSelectedControl.Location.Y - 6, currentSelectedControl.Width + 13, currentSelectedControl.Height + 13));

				// Draw resize box buttons.
				e.Graphics.DrawImage(Properties.Resources.handle, new Point(currentSelectedControl.Location.X - 10, currentSelectedControl.Location.Y - 10));
				e.Graphics.DrawImage(Properties.Resources.handle, new Point(currentSelectedControl.Location.X + (currentSelectedControl.Width / 2) - 5, currentSelectedControl.Location.Y - 10));
				e.Graphics.DrawImage(Properties.Resources.handle, new Point((currentSelectedControl.Location.X - 10) + (currentSelectedControl.Width) + 10, currentSelectedControl.Location.Y - 10));

				e.Graphics.DrawImage(Properties.Resources.handle, new Point(currentSelectedControl.Location.X - 10, currentSelectedControl.Location.Y + (currentSelectedControl.Height / 2) - 4));
				e.Graphics.DrawImage(Properties.Resources.handle, new Point((currentSelectedControl.Location.X - 10) + (currentSelectedControl.Width) + 10, currentSelectedControl.Location.Y + (currentSelectedControl.Height / 2) - 4));

				e.Graphics.DrawImage(Properties.Resources.handle, new Point(currentSelectedControl.Location.X - 10, currentSelectedControl.Location.Y + currentSelectedControl.Height));
				e.Graphics.DrawImage(Properties.Resources.handle, new Point(currentSelectedControl.Location.X + (currentSelectedControl.Width / 2) - 5, currentSelectedControl.Location.Y + currentSelectedControl.Height));
				e.Graphics.DrawImage(Properties.Resources.handle, new Point((currentSelectedControl.Location.X - 10) + (currentSelectedControl.Width) + 10, currentSelectedControl.Location.Y + currentSelectedControl.Height));
			}
		}

		private void OnPropertyBoxValueChanged(object s, PropertyValueChangedEventArgs e) {
			viewport.Refresh();
		}

		private void OnViewportMouseMove(object sender, MouseEventArgs e) {
			if (currentSelectedControl != null && !viewportMouseDown) {
				Point position = currentSelectedControl.PointToClient(MousePosition);
				position = new Point(position.X + 10, position.Y + 10);

				// Rectangles.
				Rectangle topLeft = new Rectangle(0, 0, 12, 12);
				Rectangle topCenter = new Rectangle((currentSelectedControl.Width / 2) + 5, 0, 12, 12);
				Rectangle topRight = new Rectangle(currentSelectedControl.Width + 5, 0, 12, 12);

				Rectangle middleLeft = new Rectangle(0, (currentSelectedControl.Height / 2) + 5, 12, 12);
				Rectangle middleRight = new Rectangle(currentSelectedControl.Width + 5, (currentSelectedControl.Height / 2) + 5, 12, 12);

				Rectangle bottomLeft = new Rectangle(0, currentSelectedControl.Height + 5, 12, 12);
				Rectangle bottomCenter = new Rectangle((currentSelectedControl.Width / 2) + 5, currentSelectedControl.Height + 5, 12, 12);
				Rectangle bottomRight = new Rectangle(currentSelectedControl.Width + 5, currentSelectedControl.Height + 5, 12, 12);

				// Change cursor depending on the selected item box position.
				if (position.BetweenRect(topLeft) || position.BetweenRect(bottomRight)) {
					viewport.Cursor = Cursors.SizeNWSE;
				} else if (position.BetweenRect(topCenter) || position.BetweenRect(bottomCenter)) {
					viewport.Cursor = Cursors.SizeNS;
				} else if (position.BetweenRect(topRight) || position.BetweenRect(bottomLeft)) {
					viewport.Cursor = Cursors.SizeNESW;
				} else if (position.BetweenRect(middleLeft) || position.BetweenRect(middleRight)) {
					viewport.Cursor = Cursors.SizeWE;
				} else {
					viewport.Cursor = Cursors.Default;
				}

				// Now, update resize mode depending on the selected item box position.
				if (position.BetweenRect(topLeft)) viewportResizeMode = ContentAlignment.TopLeft; else
				if (position.BetweenRect(topCenter)) viewportResizeMode = ContentAlignment.TopCenter; else
				if (position.BetweenRect(topRight)) viewportResizeMode = ContentAlignment.TopRight; else
				if (position.BetweenRect(middleLeft)) viewportResizeMode = ContentAlignment.MiddleLeft; else
				if (position.BetweenRect(middleRight)) viewportResizeMode = ContentAlignment.MiddleRight; else
				if (position.BetweenRect(bottomLeft)) viewportResizeMode = ContentAlignment.BottomLeft; else
				if (position.BetweenRect(bottomCenter)) viewportResizeMode = ContentAlignment.BottomCenter; else
				if (position.BetweenRect(bottomRight)) viewportResizeMode = ContentAlignment.BottomRight;
			} else if (currentSelectedControl != null && viewportMouseDown) {
				Point position = currentSelectedControl.PointToClient(MousePosition);

				if (position != viewportMouseDownPosition && e.Location != viewportMouseMovePosition) {
					/* Win32 fires mouse move messages when mouse button is down even if the mouse
					 * position didn't change, even if the mouse is not moving. 
					 * Save the current position and check the position in the next mouse movement
					 * to avoid resize 'loops'. */
					viewportMouseMovePosition = e.Location;

					/* Mouse is down, so the user is trying to resize the selected control.
					 * Get the resize mode, and resize and move (if necessary) the control. */
					switch (viewportResizeMode) {
						case ContentAlignment.TopLeft:
							currentSelectedControl.Height -= position.Y + viewportMouseDownPosition.Y;
							currentSelectedControl.Top += position.Y - viewportMouseDownPosition.Y;
							currentSelectedControl.Width -= position.X + viewportMouseDownPosition.X;
							currentSelectedControl.Left += position.X - viewportMouseDownPosition.X;
							break;

						case ContentAlignment.TopCenter:
							currentSelectedControl.Height -= position.Y + viewportMouseDownPosition.Y;
							currentSelectedControl.Top += position.Y - viewportMouseDownPosition.Y;
							break;

						case ContentAlignment.TopRight:
							currentSelectedControl.Height -= position.Y + viewportMouseDownPosition.Y;
							currentSelectedControl.Top += position.Y + viewportMouseDownPosition.Y;
							currentSelectedControl.Width = position.X + viewportMouseDownPosition.X;
							break;

						case ContentAlignment.MiddleLeft:
							currentSelectedControl.Left += position.X - viewportMouseDownPosition.X;
							currentSelectedControl.Width -= position.X + viewportMouseDownPosition.X;
							break;

						case ContentAlignment.MiddleRight:
							currentSelectedControl.Width = position.X + viewportMouseDownPosition.X;
							break;

						case ContentAlignment.BottomLeft:
							currentSelectedControl.Height = position.Y - viewportMouseDownPosition.Y;
							currentSelectedControl.Width -= position.X + viewportMouseDownPosition.X;
							currentSelectedControl.Left += position.X - viewportMouseDownPosition.X;
							break;

						case ContentAlignment.BottomCenter:
							currentSelectedControl.Height = position.Y - viewportMouseDownPosition.Y;
							break;

						case ContentAlignment.BottomRight:
							currentSelectedControl.Height = position.Y + viewportMouseDownPosition.Y;
							currentSelectedControl.Width = position.X + viewportMouseDownPosition.X;
							break;

						default:
							// WTF!? x3
							break;
					}
				}
			}
		}

		private void OnLogoClick(object sender, EventArgs e) {
			logoClicks++;

			if (logoClicks == 5) {
				Form wtf = new Form();
				wtf.Size = new Size(640, 480);
				wtf.StartPosition = FormStartPosition.CenterParent;
				wtf.ShowInTaskbar = false;
				wtf.ShowIcon = false;
				wtf.MaximizeBox = false;
				wtf.MinimizeBox = false;
				wtf.FormBorderStyle = FormBorderStyle.FixedDialog;
				wtf.BackgroundImage = Properties.Resources.Dogs;
				wtf.BackgroundImageLayout = ImageLayout.Stretch;
				wtf.ShowDialog(this);

				logoClicks = 0;
			}
		}

		private void menuItem1_Click(object sender, EventArgs e) {
			(new AboutDialog()).Show();
		}

		private void pictureBox1_Click(object sender, EventArgs e) {
			appMenu.Show(pictureBox1, new Point(0, pictureBox1.Height));
		}

		private void menuItem2_Click(object sender, EventArgs e) {
			Process.Start("http://anpep.tk/postedit");
		}

		private void OnViewportMouseDown(object sender, MouseEventArgs e) {
			viewportMouseDown = true;
		}

		private void OnViewportMouseUp(object sender, MouseEventArgs e) {
			viewportMouseDown = false;
		}
	}
}